import { Inject, Injectable } from '@nestjs/common';
import { RedisClientType } from 'redis';

@Injectable()
export class RedisService {
    @Inject('REDIS_CLIENT')
    private redisClient : RedisClientType

    async get(key: string) {
        return await this.redisClient.get(key);
    }

    async set(key: string, value: string | number, ttl?: number) {
        await this.redisClient.set(key, value);

        if(ttl) {
            await this.redisClient.expire(key, ttl);
        }
    }

    async keys(pattern: string) {
        return await this.redisClient.keys(pattern)
    }
    
    async listGet(key: string) {
        return await this.redisClient.lRange(key, 0, -1)
    }

    async listSet(key:string, list: Array<string>, ttl?:number) {
        for (let i = 0; i < list.length; i++) {
            await this.redisClient.lPush(key, list[i])            
        }
        if (ttl) {
            await this.redisClient.expire(key, ttl)
        }
    }

    async hashGet(key: string) {
        return await this.redisClient.hGetAll(key)
    }

    async hashSet(key: string, obj: Record<string, any>, ttl?: number) {
        for (const name in obj) {
            await this.redisClient.hSet(key, name, obj[name])
        }
        if (ttl) {
            await this.redisClient.expire(key, ttl)
        }
    }

    async geoAdd(key: string, posName: string, posLoc: [number, number]) {
        return await this.redisClient.geoAdd(key, {
            longitude: posLoc[0],
            latitude: posLoc[1],
            member: posName
        })
    }
    async geoPos(key: string, posName: string) {
        const res = await this.redisClient.geoPos(key, posName)
        return {
            name: posName,
            longitude: res[0].longitude,
            latitude: res[0].latitude,
        }
    }
    async geoList(key: string) {
        const positions = await this.redisClient.zRange(key, 0, -1)
        const list = []
        for (let i = 0; i < positions.length; i++) {
            const pos = positions[i]
            const res = await this.geoPos(key, pos)   
            list.push(res)         
        }
        return list
    }
    async geoSearch(key: string, pos: [number, number], radius: number) {
        const positions = await this.redisClient.geoRadius(key, {
            longitude: pos[0],
            latitude: pos[1]
        }, radius, 'km')
        const list = []
        for (let i = 0; i < positions.length; i++) {
            const pos = positions[i]
            const res = await this.geoPos(key, pos)   
            list.push(res)         
        }
        return list
    }

    async sAdd(key: string, ...members: string[]) {
        return this.redisClient.sAdd(key, members);
    }

    async sInterStore(newSetKey: string, set1: string, set2: string) {
        return this.redisClient.sInterStore(newSetKey, [set1, set2]);
    }

    async sIsMember(key: string, member: string) {
        return this.redisClient.sIsMember(key, member);
    }
    
    async sMember(key: string) {
        return this.redisClient.sMembers(key);
    }
    
    async exists(key: string) {
        const result =  await this.redisClient.exists(key);
        return result > 0
    } 


    async zRankingList(key: string, start: number = 0, end: number = -1) {
        const keys = await this.redisClient.zRange(key, start, end, {
            REV: true
        });
        const rankingList = {};
        for(let i = 0; i< keys.length; i++){
            rankingList[keys[i]] = await this.zScore(key, keys[i]);
        }
        return rankingList;
    }

    async zAdd(key: string, members: Record<string, number>) {
        const mems = [];
        for(let key in members) {
            mems.push({
                value: key,
                score: members[key]
            });        
        }
        return  await this.redisClient.zAdd(key, mems);
    }

    async zScore(key: string, member: string) {
        return  await this.redisClient.zScore(key, member);
    }

    async zRank(key: string, member: string) {
        return  await this.redisClient.zRank(key, member);
    }

    async zIncr(key: string, member: string, increment: number) {
        return  await this.redisClient.zIncrBy(key, increment, member)
    }

    async zUnion(newKey: string, keys: string[]) {
        if(!keys.length) {
            return []
        };
        if(keys.length === 1) {
            return this.zRankingList(keys[0]);
        }

        await this.redisClient.zUnionStore(newKey, keys);

        return this.zRankingList(newKey);
    }

}
